import { useClipboard, useDocumentVisibility, useIntervalFn, watchDebounced } from '@vueuse/core'
import { defineStore } from 'pinia'
import { reactive, watch } from 'vue'
import tinycolor from 'tinycolor2'
import { type ColorObject, ColorVartype, ColorVerifyResult, toHex, getHue, colorRegExp, ColorAliasJoint } from '@/shared/color'
import { eliminate, logError } from '@/shared/utils'
import { StorageEmit } from '@/shared/emit'
import { serverMessage } from '../utils/const'

export interface PublicState {
  select?: string
  object?: ColorObject
  searchColor?: string
  active?: string
  pin: boolean
}

export const useColorStore = defineStore('inject-color', () => {
  const state = reactive<ColorObject[]>([])

  const publicState = reactive<PublicState>({ pin: true })

  /**
   * 项目选择
   * @param value 项目名
   * @param create 如果不存在就创建项目
   */
  function select(value?: string, create = true) {
    if (value !== publicState.object?.key) {
      delete publicState.active
    }
    if (value) {
      let object = state.find((item) => item.key === value)
      if (!object && create) {
        object = {
          key: value,
          confirmed: {},
          unconfirmed: [],
          alias: {},
          vartype: ColorVartype.TYPESCRIPT,
          joint: ColorAliasJoint.TransverseLine
        }
        state.push(object)
      }
      if (object) {
        publicState.select = value
        publicState.object = object
        return
      }
    }
    publicState.select = value
    publicState.object = undefined
  }

  /**
   * 将颜色添加到当前项目中
   * @param value 颜色值
   * @param isConfirmed 添加到有效集合中
   */
  async function append(value: string, isConfirmed?: boolean) {
    if (!publicState.object) return Promise.reject()

    const color = tinycolor(value)
    const hex = toHex(color)

    const result = await validate(value)

    if (result === ColorVerifyResult.Confirmed) {
      publicState.active = hex
      return Promise.reject(`${hex} 已存在，无需重复记录`)
    }

    const { confirmed, unconfirmed } = publicState.object

    if (result === ColorVerifyResult.Unconfirmed) {
      eliminate({ array: unconfirmed, target: hex })
    }

    if (isConfirmed) {
      const hue = getHue(color)
      if (confirmed[hue]) {
        confirmed[hue].push(hex)
        confirmed[hue].sort((a, b) => parseInt(b.slice(1).padEnd(8, 'f'), 16) - parseInt(a.slice(1).padEnd(8, 'f'), 16))
      } else {
        confirmed[hue] = [hex]
      }
    } else {
      unconfirmed.unshift(hex)
    }

    publicState.active = hex

    return hex
  }

  function unappend(value: string) {
    if (!publicState.object) return Promise.reject()

    const color = tinycolor(value)

    if (!color.isValid()) return Promise.reject()

    const hex = toHex(color)
    const hue = getHue(color)

    const { confirmed, unconfirmed } = publicState.object

    if (confirmed[hue]) {
      eliminate({ array: confirmed[hue], target: hex })
    }
    eliminate({ array: unconfirmed, target: hex })
    unconfirmed.unshift(hex)

    publicState.active = hex

    return Promise.resolve()
  }

  /**
   * 从当前项目中移除该颜色
   * @param value 颜色值
   */
  async function remove(value: string) {
    if (!publicState.object) return

    const result = await validate(value)

    if (result === ColorVerifyResult.None) return

    const color = tinycolor(value)
    const hex = toHex(color)

    const { confirmed, unconfirmed, alias } = publicState.object

    delete alias[hex]
    if (result === ColorVerifyResult.Confirmed) {
      const hue = getHue(color)
      const list = confirmed[hue]
      eliminate({ array: list, target: hex })
      if (!list.length) {
        delete confirmed[hue]
      }
    } else {
      eliminate({ array: unconfirmed, target: hex })
    }

    if (publicState.active === hex) delete publicState.active
  }

  /**
   * 清除当前选中的项目
   */
  function clear() {
    delete publicState.active
    if (publicState.select) {
      eliminate({
        array: state,
        validater(item) {
          return item.key === publicState.select
        }
      })
      publicState.select = state.at(-1)?.key
      return Promise.resolve()
    }
    return Promise.reject('当前无项目')
  }

  function clearAll() {
    Object.assign<PublicState, Partial<PublicState>>(publicState, {
      active: undefined,
      searchColor: undefined,
      select: undefined,
      object: undefined
    })
    state.length = 0
  }

  /**
   * 验证一个字符串是否为有效的颜色值
   * @param value 颜色字符串
   */
  function validate(value: string): Promise<ColorVerifyResult> {
    return new Promise((resolve, reject) => {
      if (!publicState.object) return reject('请先选择项目')

      const color = tinycolor(value)
      if (!color.isValid()) return reject('无效的颜色值')

      const hex = toHex(color)

      const { confirmed, unconfirmed } = publicState.object

      if (unconfirmed.includes(hex)) {
        return resolve(ColorVerifyResult.Unconfirmed)
      }

      const hue = getHue(color)
      if (confirmed[hue]?.includes(hex)) {
        return resolve(ColorVerifyResult.Confirmed)
      }

      return resolve(ColorVerifyResult.None)
    })
  }

  let matchedText = ''

  /**
   * 从一段文字中匹配颜色字符串并收集
   * @param str 字符串
   */
  function match(str?: string) {
    if (!str || str === matchedText) return
    ;(matchedText = str).match(colorRegExp)?.forEach((value) => {
      tinycolor(value).isValid() && append(value)
    })
  }

  const { copy } = useClipboard({ legacy: true })

  if (navigator.clipboard) {
    const visibility = useDocumentVisibility()
    const { pause, resume } = useIntervalFn(
      () => {
        navigator.clipboard.readText().then(match, logError)
      },
      500,
      { immediate: !!publicState.object?.realTimeMatching }
    )

    watch([visibility, () => publicState.object?.realTimeMatching], ([visible, realTimeMatching]) => {
      visible === 'visible' && realTimeMatching ? resume() : pause()
    })
  }

  const storage = new StorageEmit<{ color: string }>({ color: JSON.stringify([]) })

  storage.immediate('color', (value) => {
    if (value && JSON.stringify(state) !== value) {
      try {
        const json = JSON.parse(value)
        if (Array.isArray(json)) {
          state.length = 0
          state.push(...json)
        }
      } catch (error) {
        console.error(error)
      }
    }
  })

  watchDebounced(
    state,
    () => {
      storage.set('color', JSON.stringify(state))
      select(publicState.select)
    },
    { debounce: 1000, maxWait: 1000 * 5 }
  )

  serverMessage.on('inject', () => {
    publicState.pin = true
  })

  watch(() => publicState.active, console.log)

  return { state, publicState, select, append, unappend, remove, clear, clearAll, validate, match, copy }
})
